package cn.funnymap.lgis.response;

import cn.funnymap.lgis.response.exception.AbstractException;
import cn.funnymap.lgis.response.exception.common.BaseException;
import cn.funnymap.lgis.response.exception.common.ParamValidationException;
import cn.funnymap.lgis.response.status.code.BaseStatusEnum;
import cn.funnymap.lgis.response.status.code.ValidationExceptionStatusCodeEnum;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import net.dreamlu.mica.auto.annotation.AutoService;
import org.jetbrains.annotations.NotNull;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.core.MethodParameter;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 实现ResponseBodyAdvice接口，统一异常拦截及统一响应
 *
 * @author jiaoxn
 */
@ConditionalOnClass(RestController.class)
@Slf4j
@AutoService(ResponseBodyAdvice.class)
@RestControllerAdvice(annotations = RestController.class)
public class GlobalResponseAdvice implements ResponseBodyAdvice<Object> {
    @Override
    public boolean supports(MethodParameter returnType, @NotNull Class<? extends HttpMessageConverter<?>> converterType) {
        // 如果返回类型是SseEmitter，则不拦截
        return !SseEmitter.class.isAssignableFrom(returnType.getParameterType());
    }

    @Override
    public Object beforeBodyWrite(Object body,
                                  @NotNull MethodParameter returnType,
                                  @NotNull MediaType selectedContentType,
                                  @NotNull Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                  @NotNull ServerHttpRequest request,
                                  @NotNull ServerHttpResponse response) {
        // 如果响应是流数据（对应下载的情况），直接返回
        if (body instanceof InputStreamResource) {
            return body;
        }

        // 如果响应是二进制数据（对应下载的情况），直接返回
        if (body instanceof byte[]) {
            return body;
        }

        // 修改响应标头的Content-Type为application/json
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);

        ResponseStructure<Object> responseStructure;

        // 针对不同的请求方法设置不同的响应消息
        switch (((ServletServerHttpRequest) request).getServletRequest().getMethod().toUpperCase()) {
            case "POST":
                response.setStatusCode(HttpStatus.CREATED);
                responseStructure = ResponseStructure.created("资源创建成功");
                break;
            case "PUT":
                responseStructure = ResponseStructure.success("资源更新成功");
                break;
            case "DELETE":
                responseStructure = ResponseStructure.success("资源删除成功");
                break;
            default:
                responseStructure = ResponseStructure.success();
        }

        responseStructure.setData(body);

        // 如果函数返回值是字符串类型，则将其转为JSON字符串并返回
        if (returnType.getParameterType() == String.class) {
            return JSONObject.toJSONString(responseStructure);
        } else {
            responseStructure.setData(body);
        }

        return responseStructure;
    }

    /**
     * 拦截自定义异常
     */
    @ExceptionHandler({AbstractException.class})
    public ResponseEntity<ResponseStructure<String>> handleCustomException(AbstractException customException) {
        this.writeExceptionToLog(customException);

        ResponseStructure<String> responseStructure = ResponseStructure.fail(customException);
        return new ResponseEntity<>(responseStructure, customException.getHttpStatus());
    }

    /**
     * 拦截参数校验异常
     */
    @ExceptionHandler({ConstraintViolationException.class})
    public ResponseEntity<ResponseStructure<String>> handleCustomException(ConstraintViolationException exception) {
        List<String> msgList = exception.getConstraintViolations()
                .stream()
                .map(ConstraintViolation::getMessage)
                .collect(Collectors.toList());
        ParamValidationException paramValidationException =
                new ParamValidationException(ValidationExceptionStatusCodeEnum.ERR_INVALID_REQUEST_PARAMS,
                        String.join("；", msgList));

        this.writeExceptionToLog(paramValidationException);

        ResponseStructure<String> responseStructure = ResponseStructure.fail(paramValidationException);
        return new ResponseEntity<>(responseStructure, paramValidationException.getHttpStatus());
    }

    /**
     * Validation校验异常
     */
    @ExceptionHandler({ValidationException.class})
    public ResponseEntity<ResponseStructure<String>> handleValidationException(ValidationException exception) {
        ParamValidationException paramValidationException =
                new ParamValidationException(ValidationExceptionStatusCodeEnum.ERR_INVALID_REQUEST_PARAMS,
                        exception.getMessage());

        this.writeExceptionToLog(paramValidationException);

        ResponseStructure<String> responseStructure = ResponseStructure.fail(paramValidationException);
        return new ResponseEntity<>(responseStructure, paramValidationException.getHttpStatus());
    }

    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    @ExceptionHandler({BindException.class})
    public ResponseEntity<ResponseStructure<String>> handleBindException(BindException exception) {
        String message = exception.getBindingResult().getAllErrors().get(0).getDefaultMessage();

        ParamValidationException paramValidationException =
                new ParamValidationException(ValidationExceptionStatusCodeEnum.ERR_INVALID_REQUEST_PARAMS,
                        message);

        this.writeExceptionToLog(paramValidationException);

        ResponseStructure<String> responseStructure = ResponseStructure.fail(paramValidationException);
        return new ResponseEntity<>(responseStructure, paramValidationException.getHttpStatus());
    }

    /**
     * 顶级异常捕获，当其他异常无法处理时选择使用
     */
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler({Exception.class})
    public ResponseEntity<ResponseStructure<String>> handleGlobalException(Exception exception, NativeWebRequest request) {
        // 如果当前是SSE请求，则跳过统一异常拦截
        if (this.isSseEmitterRequest(request)) {
            return null;
        }
        
        String message = String.format("未知异常：%s",
                Optional.ofNullable(exception.getCause())
                        .map(Throwable::getMessage)
                        .orElse(exception.getMessage())
        );
        log.error(message, exception);

        BaseException baseException = new BaseException(BaseStatusEnum.ERR_UNKNOWN, message);
        ResponseStructure<String> responseStructure = ResponseStructure.fail(baseException);
        return new ResponseEntity<>(responseStructure, baseException.getHttpStatus());
    }
    private boolean isSseEmitterRequest(NativeWebRequest request) {
        HttpServletResponse response = ((ServletWebRequest) request).getResponse();
        if (response == null) {
            return false;
        }

        String contentType = response.getContentType();
        if (contentType == null || contentType.isEmpty()) {
            return false;
        }

        return contentType.startsWith("text/event-stream");
    }

    private void writeExceptionToLog(AbstractException exception) {
        String errorMessage = String.format("错误代码：%s，错误描述信息：%s", exception.getCode(), exception.getMessage());
        log.error(errorMessage, exception);
    }
}
